﻿
#define UseTryCatchInExpressionTree

using System;
using System.Collections.Generic;
using System.Web;

using System.Linq.Expressions;

using System.Reflection;
using System.Text.RegularExpressions;
using System.IO;
using System.Runtime.Caching;

namespace Nana.Web.SSIHandler
{
  public static class AutoDetectSSIHelper
  {
    private class InternalIOException : IOException
    {
      public InternalIOException(string message, Exception inner)
        : base(message, inner) { }
      public InternalIOException(Exception inner)
        : this(inner.Message, inner) { }
    }


    private static MemoryCache writerCache = new MemoryCache("AutoDetectSSIHandler/WriterCache");
    private static string GetResString(string name)
    {
      return name;
    }

    private static readonly Regex findInclude =
      new Regex(@"<!--\s*#\s*include\s+(file|virtual)\s*=\s*""([^""<>]+)""\s*-->",
        RegexOptions.IgnoreCase);

    private static readonly string msgDeviceNotReady = "[" + GetResString("Device is not ready") + "]";
    private static readonly Action<TextWriter> deviceNotReady =
      tw => tw.Write(msgDeviceNotReady);

    //private static readonly Type typeString = typeof(string);
    //private static readonly Type typeTextWriter = typeof(TextWriter);
    private static readonly MethodInfo writeMethod =
      typeof(TextWriter).GetMethod("Write", new[] { typeof(string) });
    // 使用这种迂回的方式是为了从代码重构功能中受益
    private static readonly Expression<Action> xpTestGetDelegate =
      () => (null as HttpContext).GetAutoDelegate(null);
    private static readonly MethodInfo getDelegateMethod =
      (xpTestGetDelegate.Body as MethodCallExpression).Method;

    private static readonly Expression xpDeviceNotReady =
      Expression.Constant(deviceNotReady, typeof(Action<TextWriter>));
    private static readonly ParameterExpression paramWriter =
      Expression.Parameter(typeof(TextWriter));
    private static readonly CatchBlock xpCatchAndWarnDeviceNotReady =
      Expression.MakeCatchBlock(typeof(InternalIOException), null, // 捕获内部异常，输出异常提示信息
            Expression.Invoke(xpDeviceNotReady, paramWriter), null);

    static AutoDetectSSIHelper()
    {
    }

    private static Action<TextWriter> NewAutoDelegate(this HttpContext context, string file)
    {
      string srcCode;
      try
      {
        //using (var sr = new StreamReader(file, System.Text.Encoding.Default))
        using (var sr = new StreamReader(file))
        {
          // 读取全部的文本内容
          srcCode = sr.ReadToEnd();
        }
      }
      catch (Exception ex)
      {
        // 无法打开文件，抛出
        throw new InternalIOException(ex);
      }

      // 查找 include 指令
      Match m = findInclude.Match(srcCode);
      // 若不存在则生成简单的输出委托
      if (!m.Success)
        return tw => tw.Write(srcCode);


      // 初始化动态构造的过程中需要的集合
      var callsBlock = new LinkedList<Expression>();

      int lastIndex = 0;
      do
      {
        #region 定位被包含文件的物理路径

        var inc = m.Groups[2].Value;
        switch (m.Groups[1].Value[0])
        {
          case 'v':
          case 'V':
            // 虚拟应用程序路径
            //inc = HttpContext.Current.Server.MapPath(inc);
            inc = context.Server.MapPath(inc);
            break;
          default:
            // 相对路径
            inc = Path.Combine(Path.GetDirectoryName(file), inc);
            break;
        }

        #endregion

        #region 动态构造代码，用于输出此文件以及被包含文件中的内容

        // 静态部分
        callsBlock.AddLast(Expression.Call(paramWriter, writeMethod,
          Expression.Constant(srcCode.Substring(lastIndex, m.Index - lastIndex), typeof(string))));
        lastIndex = m.Index + m.Length;

        // 包含部分
#if UseTryCatchInExpressionTree
        callsBlock.AddLast(Expression.TryCatch(
          Expression.Invoke(
            Expression.Call(getDelegateMethod, // 使用同样的手段获得被包含文件的输出委托
              Expression.Constant(context, typeof(HttpContext)),
              Expression.Constant(inc, typeof(string))), paramWriter),
          xpCatchAndWarnDeviceNotReady));
#else
        callsBlock.AddLast(Expression.Invoke(
          Expression.Call(getWriterMethod,
          Expression.Constant(inc, typeof(string))), pWriter));
#endif

        #endregion

        //
        m = m.NextMatch();
      } while (m.Success);

      // 添加最后一部分静态内容
      callsBlock.AddLast(Expression.Call(paramWriter, writeMethod,
        Expression.Constant(srcCode.Substring(lastIndex), typeof(string))));

      // 编译并返回动态构造的输出委托
      return Expression.Lambda<Action<TextWriter>>(Expression.Block(callsBlock), paramWriter).Compile();
    }
    public static Action<TextWriter> GetAutoDelegate(this HttpContext context)
    {
      return context.GetAutoDelegate(context.Request.PhysicalPath);
    }
    public static Action<TextWriter> GetAutoDelegate(this HttpContext context, string file)
    {
      Lazy<Action<TextWriter>> writerDelegate = (Lazy<Action<TextWriter>>)writerCache[file];
      if (null == writerDelegate)
      {
        #region
#if !UseTryCatchInExpressionTree
        try
        {
#endif
        #endregion

        var policy = new CacheItemPolicy();
        try
        {
          policy.ChangeMonitors
            .Add(new HostFileChangeMonitor(new[] { file }));
        }
        catch (Exception ex)
        {
          throw new InternalIOException(ex);
        }


        writerDelegate = new Lazy<Action<TextWriter>>(() => context.NewAutoDelegate(file));

        writerDelegate = (Lazy<Action<TextWriter>>)writerCache
          .AddOrGetExisting(file, writerDelegate, policy) ?? writerDelegate;
        #region
#if !UseTryCatchInExpressionTree
        }
        catch (InternalIOException)
        {
          return tw => tw.Write("[File Not Found]");
        }
#endif
        #endregion
      }
      return writerDelegate.Value;
    }
  }
}
